分类
联系方式
  1. 新浪微博
  2. E-mail

DartVM Isolate

介绍

Flutter/Dart 代码是事件驱动的,代码运行在 Isolate 当中。对于 Dart 开发来说,Isolate 相当于线程,基于 Isolate 可以实现无锁并发。

Isolate 之间不能相互访问,他们需要通过 Port 机制相互通信。

每个 isolate 都有一个关联线程(mutator thread),用于执行 Dart 代码。

针对 Isolate 有两个视角:开发者使用视角、实现原理视角。

开发者视角:Isolate 间通信

Isolate 间通信分类两种情况:单向通信和双向通信。

单向通信

Isolate 间通信的示例:

void demo() {
    ReceivePort port = ReceivePort();
    // 创建 isolate
    isolate.spawn(func, port.sendPort);
    
    port.listen((message) {
        print(message)
    });
}

void func(SendPort sendPort) {
    sendPort.send(1)
}

其中:

  • 创建了一个 ReceivePort,它是当前 Isolate 与新建 Isolate 沟通的桥梁
  • 通过 isolate.spawn 创建了一个新 Isolate,并传入 ReceivePort 的 sendPort
  • sendPort 是新 isolate 向老 Isolate 回传数据的一端
  • 老 Isolate 通过对 ReceivePort 进行监听获取数据

双向通信

示例代码:

Future<void> demo() async {
    // 接收新 Isolate 回传数据
    final response = ReceivePort();
    
    // 创建新 Isolate
    await Isolate.spawn(func, response.sendPort);
    
    // 新 Isolate 传回来一个 SendPort,用于向 Isolate 发送数据
    final sendPort = await response.first as SendPort;
}

void func(SendPort sendPort) async {
    final port = ReceivePort();
    
    // 接收老 Isolate 传来数据
    sendPort.send(port.sendPort);
}

上面代码中,只包含了双向通信打通的代码,没有包含消息监听的代码。

可以看到,SendPort 是可以跨 Isolate 传递的。

注:为啥 response 可以 await first 呢?因为 ReceivePort 实现了 Stream 接口。

Isolate 实现原理

Isolate 在虚拟机中有两个实现类:

  • isolate.h:Isolate 的具体实现,runtime/vm/isolate.h
  • isolate.dart:在 Dart 侧的映射,将操作转发到底层
    • 接口 sdk/lib/isolate/isolate.dart
    • 原生实现 sdk/lib/_internal/vm/lib/isolate_patch.dart

Isolate 是虚拟机底层特性,具体的实现都在 C/C++ 层中。Dart 侧的 Isolate 是 C 层在 Dart 层的映射,供 Dart 层访问 Isolate,具体操作都是转发到底层处理。

Isolate 内部由以下部分组成:

Isolate组成
  • Isolate 堆(Heap):由垃圾回收器 GC 管理的存储,这个 isolate 里运行的代码,创建的所有对象,都在这个堆中进行管理。
  • mutator thread:每个 isolate 都有一个 mutator thread,用于执行 Dart 代码。
  • MessageQueue:消息队列,细节:
    • Isolate 的 MessageQueue 不是一个死循环,而是按需启动的。
      • 当有新消息插入到 MessageQueue,如果 Isolate 尚未启动,VM 会将消息处理器以任务形式交给线程池,由线程池为其分配一个线程,在分配的线程上执行任务处理
      • 从消息队列中取消息执行。如果消息队列为空,所有任务都执行完了,会出让该线程。
      • 总结:Isolate 并不会长期占用一个线程,而是所有 Isolate 共享一个线程池。
    • 消息队列的种类
      • Isolate 消息处理器中有两种消息队列:普通消息队列和 OOB 消息队列。
      • OOB 全称是 out of band,表示带外消息,用来传送一些控制类消息。如暂停(Pause)、恢复(Resume)、终止(Kill)
  • helper thread:helper thread 不只一个,不同的线程执行不同工作,比如:
    • GC 清理
    • JIT 编译
    • GC 并发标记

Isolate.kill 终止操作

在 Dart 侧可以调用 Isolate 的 kill 方法终止 Isolate,Dart 侧 Isolate 会通过消息队列机制调用 C 层 Isolate,具体流程是:

  1. Dart Isolate kill 方法被调用
  2. 生成一条 OOB Message,扔到消息队列上
  3. IsolateMessageHandler 响应 OOB Message
  4. 调用 C 层 Isolate 相关实现

注释介绍:请求 Isolate 终止。

需传入一个 priority 参数,有两种优先级:

  • immediate:isolate 尽快终止
    • 在 OOB 队列上,控制信息是按顺序处理的,因此消息队列上在终止信息之前的控制消息都会被处理。
    • 终止完成的时间节点,不会比 beforeNextEvent 晚
  • beforeNextEvent:
    • 在当前事件和消息对列上已有的控制事件完成后,终止操作被安排在下一次 Isolate 收到控制信号的时候

kill 操作的方法签名:

external void kill({int priority = beforeNextEvent});

如果 terminateCapability 为空,或者它不是 controlPort 所标示的 Isolate 终止能力,则 Isolate 将忽略该终止请求。

具体实现:

Dart 侧 Isolate kill 方法被调用时,实际是发送一条 OOB 消息,抛到消息队列,isolate.dart(sdk/lib/_internal/vm/lib/isolate_patch.dart):

@patch
void kill({int priority: beforeNextEvent}) {
  var msg = new List<Object?>.filled(4, null)
    ..[0] = 0 // Make room for OOB message type.
    ..[1] = _KILL
    ..[2] = terminateCapability
    ..[3] = priority;
  _sendOOB(controlPort, msg);
}

这条消息在 IsolateMessageHandler 的 IsolateMessageHandler::HandleLibMessage 中被处理:

case Isolate::kKillMsg:
case Isolate::kInternalKillMsg: {
  // [ OOB, kKillMsg, terminate capability, priority ]
  const intptr_t priority = Smi::Cast(obj).Value();
  // kImmediateAction
  if (priority == Isolate::kImmediateAction) {
    obj = message.At(2);
    if (I->VerifyTerminateCapability(obj)) {
      // We will kill the current isolate by returning an UnwindError.
      if (msg_type == Isolate::kKillMsg) {
        const String& msg = String::Handle(
            String::New("isolate terminated by Isolate.kill"));
        const UnwindError& error =
            UnwindError::Handle(UnwindError::New(msg));
        error.set_is_user_initiated(true);
        return error.ptr();
      } else if (msg_type == Isolate::kInternalKillMsg) {
        const String& msg = 
            String::Handle(String::New("isolate terminated by vm"));
        return UnwindError::New(msg);
      } else {
        UNREACHABLE();
      }
    } else {
      return Error::null();
    }
  } else {
      // kBeforeNextEventAction kAsEventAction
      message.SetAt(
          0, Smi::Handle(zone, Smi::New(Message::kDelayedIsolateLibOOBMsg)));
      message.SetAt(3,
          Smi::Handle(zone, Smi::New(Isolate::kImmediateAction)));
      this->PostMessage(
          SerializeMessage(Message::kIllegalPort, message),
          priority == Isolate::kBeforeNextEventAction /* at_head */);
  }
  break;
}

这里面 if 嵌套有点多,最外层的判断优先级:kImmediateAction 和 kBeforeNextEventAction。

先看 kImmediateAction 的处理:

  • 这里有个很重要的操作,VerifyTerminateCapability,如果认证失败的话,会什么都不做 return Error::null();
  • 接下来要看 msg_type,就是 Switch 的 case,有两个可能 kKillMsg、kInternalKillMsg,Isolate.kill 发的值是 4,也就是 kKillMsg
  • 如果是 kKillMsg,让 Isolate 停下来的方式,是抛出一个 UnwindError,这是一个威力特别大的 Error,抛出后 Isolate 就会终止
  • 不论是 kKillMsg 还是 kInternalKillMsg。停 isolate 的手段是一样的,都是靠扔 UnwindError,区别在于 Error message 不一样

再看 kBeforeNextEventAction:

  • 它是对 OOB 消息进行了修改
  • 并重新扔到 OOB 消息队列中,新的消息的类型已经是 kImmediateAction
  • 注意到 PostMessage 的第二个参数 priority,在消息队列执行出队的时候,只会出该优先级之上的消息

问题:kImmediateAction 终止不了

我在实践中遇到一个问题,Isolate.kill 传 kImmediateAction,结果 Isolate 没有终止。

如何看 Isolate 是否成功终止?去 Observatory 里面,他会展示运行中的 Isolate,如果终止成功,在 Observatory 里就没有这个 Isolate 了。

从命令行上来看,也有杀死的日志:[VERBOSE-2:shell.cc(93)] Dart Error: isolate terminated by Isolate.kill

问题的原因找到了,我是在 Demo 里,摆了一个按钮,在按钮回调中调用 Isolate.kill 的 kImmediateAction 优先级,这时候 Isolate.kill 会阻塞住执行不完。

如果我把 Isolate.kill 的 kImmediateAction 调用用 Future 封装一下再在点击回调中调用,就能成功终止了。

由此看来,点击回调跟 Isolate.kill 的 kImmediateAction 在消息队列上是存在某种冲突。冲突的原因还需要后续深挖。

问题:Future + kImmediateAction vs kBeforeNextEventAction 谁快?

尽管注释说 kImmediateAction 终止完成的时间节点,不会比 beforeNextEvent 晚,但这是没有套 Future 的时候。

一个 Future(Normal Message)的优先级是低于 OOB Message 的。

因此,Future + kImmediateAction vs kBeforeNextEventAction,kBeforeNextEventAction 速度更快。

Kernel Isolate

又被称为 Kernel Service。代码位于 runtime/vm/kernel_isolate.h。通过阅读参考文献1,直到它的核心工作时 Common front end,负责将 Dart 源码转为 Kernel 二进制,Dart VM 可以接收这个二进制在主 Isolate 里面运行。

网络资源

Dart虚拟机运行原理

深入理解Flutter/Dart事件机制

Flutter(二十五)-Dart中多线程Isolate